iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0
Software Development

燃燒大三的成果發表系列 第 10

燃燒大三的成果發表第十天 - Decorator(裝飾器)

  • 分享至 

  • xImage
  •  

first-class function

在說明裝飾器之前,我想先介紹「一級函式」英文稱作「first-class function」,在python中的函數就是first-class function,那什麼是first-class function,這邊我簡單介紹屬於兩點概念

  1. function能夠作為其他function的參數
  2. 能作為function的return的值

first-class function還有其他概念就不一一介紹,我們馬上先來看看範例:

def decorator_func(func):
    def wrapper(*args, **kwargs): 
        result = func(*args, **kwargs)
        print("共用額外的內容以及程式碼")
        return result
    return wrapper  # wrapper作為decorator_func的return的值

def parameter_func(Text):
    print(Text)

if __name__ == "__main__":
    insatance = decorator_func(parameter_func)  # 把paramter_func作為Decorator_func的參數,上述提到first-class function的其中一點
    insatance("鐵人賽") # 再輸入parameter_func的參數Text
    # 上面兩行也可以併成 decorator_func(parameter_func)("鐵人賽")

第6行可以看得出來 1. paramter_func作為Decorator_func的參數
第12行可以看得出來 2. wrapper作為decorator_func的return的值

裝飾器為function

「裝飾器」英文稱作「Decorator」也是我們今天的主題,我們馬上來看看假設我最一開始有兩個function長這樣:

def decorator_func(Text):
    print("共用額外的內容以及程式碼")
    return parameter_func(Text)

def parameter_func(Text):
    print(Text)

if __name__ == "__main__":
    decorator_func("鐵人賽")

今天如果我多了一個parameter_add_func,也想要被decorator_func裝飾的話,又要改成這樣:

def decorator_func(number1, number2):
    print("共通額外的內容以及程式碼")
    return parameter_add_func(number1, number2)

def parameter_add_func(number1, number2):
    print("Total = ", number1 + number2)

if __name__ == "__main__":
    decorator_func(1, 2)

今天如果我想要這兩個function都想要被裝飾(都能夠得到共通額外的程式碼),那我就可以這樣:

def decorator_func(func):
    def wrapper(*args, **kwargs):
        print("共用額外的內容以及程式碼")
        return func(*args, **kwargs)
    return wrapper

def parameter_add_func(number1, number2):
    print("Total = ", number1 + number2)

def parameter_func(Text):
    print(Text)

if __name__ == "__main__":
    decorator_func(parameter_add_func)(1, 2)
    decorator_func(parameter_func)("鐵人賽")

這樣就可以把上述兩個範例做合併,就不用獨自分開來寫,那我的理解是裝飾器名符其實,他的功用就是要裝飾其他function,正如我上述舉例用decorator_func去裝飾那兩個「parameter_func」、「parameter_add_func」

了解基本裝飾器的概念,以及first-class function,我們還可以將上面的範例改成使用@(語法糖)的話會變成這樣:

  1. Decorator本身沒有參數的情況

    def decorator_func(func):
        def wrapper(*args, **kwargs): 
            result = func(*args, **kwargs)
            print("共用額外的內容以及程式碼")
            return result
        return wrapper
    
    @decorator_func
    def parameter_func(Text):
        print(Text)
    
    if __name__ == "__main__":
        parameter_func("鐵人賽")  # decorator_func(parameter_func)("鐵人賽")
    
  2. Decorator本身需要有參數的情況

    def decorator_func(Text):
        def wrapper(func):
            def wrapper_1(*args, **kwargs):
                result = func(*args, **kwargs)
                print(Text)
                return result
            return wrapper_1
        return wrapper
    
    
    @decorator_func("共用額外的內容以及程式碼")
    def parameter_func(Text):
        print(Text)
    
    
    if __name__ == "__main__":
        parameter_func("鐵人賽")  # decorator_func("decorator_func wrapper_1 inside")(parameter_func)("鐵人賽")
    

簡單來說未來只要你有多個function需要加上額外的功能,可以直接在被裝飾的function上面加上@(語法糖),如此一來就可以省時省力省空間,不但不需要在一個一個重寫,呼叫的時候也不需要寫的很長。

  1. 多個裝飾器
    以上的介紹都是使用在一個裝飾器的情況底下,當然有一就有二如果多個裝飾器,他會有著從上而下的一個巢狀結構像是這樣:
def decorator_func2(func):
    def wrapper(*args, **kwargs):
        print("decorator_func2 in") 
        result = func(*args, **kwargs)
        print("decorator_func2 out")
        return result
    return wrapper

def decorator_func(func):
    def wrapper(*args, **kwargs): 
        print("decorator_func in")
        result = func(*args, **kwargs)
        print("decorator_func out")
        return result
    return wrapper

@decorator_func2
@decorator_func
def parameter_func(Text):
    print(Text)

if __name__ == "__main__":
    parameter_func("鐵人賽")
    
# output
# decorator_func2 in
# decorator_func in
# 鐵人賽
# decorator_func out
# decorator_func2 out

可以看得出來他會從最上面的裝飾器開始進入,「decorator_func2」裝飾「decorator_func」再裝飾「parameter_func」

裝飾器為Class

裝飾器可以用function裝飾function,一般來說我們在寫程式的時候,會以Class為架構,所以今天假設我們Class架構的底下有一個裝飾器,就能夠使用這種方法,在這之前我先來講一下所需要的概念。

class Decorator_Class():
    def __init__(self, Text):
        self.Text = Text
    
    def __call__(self, *args, **kwargs):
        print("call in")
        print("內有裝飾的程式碼")
        print(self.Text)
        print("call out")


if __name__ == "__main__":
    instance = Decorator_Class("鐵人賽")
    instance()  # 將class變成可被呼叫
    # call in
    # 內有裝飾的程式碼
    # 鐵人賽
    # call out

透過__call__內建function我們可以把class的實例變成可以呼叫的,所以現在實例就像是function一樣,如果我們加上了裝飾器的情況:

  1. Decorator本身沒有參數的情況
class Decorator_Class():
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print("call in")
        print("內有裝飾的程式碼")
        result = self.func(*args, **kwargs)
        print("call out")
        return result


@Decorator_Class
def parameter_function(Text):
    print(Text)


if __name__ == "__main__":
    parameter_function("鐵人賽")

其實與上面的例子並無不同,大致上一樣,只是我們透過__call__將實例變成function一樣。

  1. Decorator本身需要有參數的情況
class Decorator_Class():
    def __init__(self, Text):
        self.Text = Text
    
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print("call in")
            result = func(*args, **kwargs)
            print(self.Text)
            print("call out")
        return wrapper


@Decorator_Class("內有裝飾的程式碼")
def parameter_function(Text):
    print(Text)


if __name__ == "__main__":
    parameter_function("鐵人賽")
  1. 多個裝飾器
class Decorator_Class():
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print("call in")
        result = self.func(*args, **kwargs)
        print("call out")
        return result
    
    @staticmethod
    def decorator_2(func):
        def wrapper(*args, **kwargs):
            print("decorator_2 in")
            result = func(*args, **kwargs)
            print("decorator_2 out")
            return result
        return wrapper

@Decorator_Class
@Decorator_Class.decorator_2
def parameter_function(Text):
    print(Text)


if __name__ == "__main__":
    parameter_function("鐵人賽")

# call in
# decorator_2 in
# 鐵人賽
# decorator_2 out
# call out

其實裝飾器可以擺很多地方,會有很多種狀況,就是基本的搞懂了就行,寫到最後我頭也暈暈,非常簡單下一個小總結,裝飾器其實就像是俄羅斯娃娃,一層裝飾一層,但是裝飾器是非常重要的用法,明天我要來分享Iterator


上一篇
燃燒大三的成果發表第九天 - 封裝
下一篇
燃燒大三的成果發表第十一天 - Iterator
系列文
燃燒大三的成果發表30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言